package cri

import (
	"context"
	"sync"
	"syscall"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/namespaces"
	"github.com/containerd/containerd/oci"
	"github.com/opencontainers/runtime-spec/specs-go"
	"k8s.io/klog/v2"
)

const (
	ContainerdSock = "/run/containerd/containerd.sock"
)

var (
	client *containerd.Client
	once   sync.Once = sync.Once{}
)

func NewContainerdClient() (*containerd.Client, error) {
	var err error
	var c *containerd.Client
	once.Do(func() {
		c, err = containerd.New(ContainerdSock)
		if err != nil {
			klog.Warning("Unable to initialize containerd client. Extensions won't be launched. This may affect functionalities of virtual machines")
		}
		client = c
	})
	return client, err
}

// Create a container without launch
func CreateContainer(ctx context.Context, c *containerd.Client, id, image, snapshotId string, netns string, args ...string) (containerd.Container, error) {

	img, err := c.GetImage(ctx, image)
	if err != nil {
		klog.Info("Unable to find image locally. Pulling", err)
		img, err = c.Pull(ctx, image, containerd.WithPullUnpack)
		if err != nil {
			return nil, err
		}
	}

	container, err := c.NewContainer(ctx, id,
		containerd.WithNewSnapshot(snapshotId, img),
		containerd.WithNewSpec(
			oci.WithImageConfig(img),
			oci.WithProcessArgs(args...),
			oci.WithLinuxNamespace(
				specs.LinuxNamespace{
					Type: specs.NetworkNamespace,
					Path: netns,
				},
			),
		),
	)
	if err != nil {
		return nil, err
	}
	err = CreateNetworkNamespace(netns)
	if err != nil {
		return nil, err
	}

	if err != nil {
		return nil, err
	}
	return container, nil

}

// LaunchContainer launch a created container
func LaunchContainer(ns string, container containerd.Container) {
	ctx := namespaces.WithNamespace(context.Background(), ns)
	task, err := container.NewTask(
		ctx,
		cio.NewCreator(cio.WithStdio),
	)
	if err != nil {
		klog.Warning("Fail to launch task ", err)
		return
	}

	// TODO: Add CNI to attach containers to bridge
	statC, err := task.Wait(ctx)
	if err != nil {
		klog.Warning("Fail to wait ", err)
		return
	}

	err = task.Start(ctx)
	if err != nil {
		klog.Warning("Fail to start", err)
		return
	}

	status := <-statC
	klog.Info("container: ", container.ID(), "exitted with code:", status.ExitCode())

}

// DeleteContainers in given namespace
func DeleteContainers(c *containerd.Client, ns string) error {
	ctx := namespaces.WithNamespace(context.Background(), ns)

	containers, err := c.Containers(ctx)
	if err != nil {
		return err
	}
	for _, container := range containers {
		t, err := container.Task(ctx, func(f *cio.FIFOSet) (cio.IO, error) {
			return cio.NullIO("")
		})
		if err != nil {
			klog.Warning("Error getting task for container: ", container.ID, "Err: ", err)
			klog.Warning("Skip task deletion")
		} else {
			err = t.Kill(ctx, syscall.SIGKILL)
			t.Delete(ctx)
		}
		if err != nil {
			klog.Warning(err)
		}
		err = container.Delete(ctx, containerd.WithSnapshotCleanup)
		if err != nil {
			klog.Warning(err)
		}
	}
	return nil
}
